import React, { ReactNode, useReducer, useRef, useEffect, useMemo, createContext, Dispatch } from 'react';

export enum ActionType {
    ADD_COUNT = 'ADD_COUNT',
    ADD_NUM = 'ADD_NUM',
    SET_LOADING = 'SET_LOADING',
}

export type State = {
    count: number;
    num: number;
    loading: boolean;
};

type AddCountAction = {
    type: ActionType.ADD_COUNT;
};

type AddNumAction = {
    type: ActionType.ADD_NUM;
};
type SetLoadingAction = {
    type: ActionType.SET_LOADING;
    loading: boolean;
};

const initialState: State = { count: 0, num: 0, loading: false };

export const MyContext = createContext<{
    getState: () => State;
    subscribe: any;
    dispatch: Dispatch<AddCountAction | AddNumAction | SetLoadingAction>;
}>({
    getState: () => initialState,
    subscribe: () => {},
    dispatch: () => {},
});

// useContext的方式
function reducer(state: State, action: AddCountAction | AddNumAction | SetLoadingAction) {
    switch (action.type) {
        case ActionType.ADD_COUNT: {
            return {
                ...state,
                count: state.count + 1,
            };
        }
        case ActionType.ADD_NUM: {
            return {
                ...state,
                num: state.num + 1,
            };
        }
        case ActionType.SET_LOADING: {
            return {
                ...state,
                loading: action.loading,
            };
        }
        default:
            return state;
    }
}

export function MyProvider({ children }: { children: ReactNode }) {
    const [state, dispatch] = useReducer(reducer, initialState);

    // ref state
    const stateRef = useRef<State>(initialState);
    stateRef.current = state;

    // ref 订阅回调数组
    const subscribersRef = useRef<any[]>([]);

    // state 变化，遍历执行回调
    useEffect(() => {
        subscribersRef.current.forEach((sub: any) => sub());
    }, [state]);

    // 缓存 value， 利用 ref 拿到最新的 state, subscribe 状态
    const value = useMemo(
        () => ({
            dispatch,
            subscribe: (cb: any) => {
                subscribersRef.current.push(cb);
                return () => {
                    subscribersRef.current = subscribersRef.current.filter((item) => item !== cb);
                };
            },
            getState: () => stateRef.current,
        }),
        [],
    );

    return <MyContext.Provider value={value}>{children}</MyContext.Provider>;
}
